/*
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is
 *
 * java-concurrency-presentation:
 * concurrency presentation tutorial.
 *
 * The Initial Developer of the Original Code is
 * Marco Antonio Villalobos, Jr.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): None.
 *
 * ***** END LICENSE BLOCK *****
 */

package presentation.concurrency.slides;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

/**
 * Bounded Set example used to show how to use semaphore.
 */
public class BoundedHashSet<T> {

    /** The data-structure containing the items add into this set. */
    private final Set<T> items;

    /** The semaphore used in this example. */
    private final Semaphore semaphore;

    /**
     * Initializes this set with its maximum capacity.
     *
     * @param capacity The maximum capacity of this set.
     */
    public BoundedHashSet(int capacity) {
        this.items = Collections.synchronizedSet(new HashSet<T>(capacity));
        this.semaphore = new Semaphore(capacity);
    }


    /**
     * Adds an item to this set.  Will be blocked if the set is full.
     *
     * @param item The item to add to the set.
     * @return true if the item was added, false otherwise.
     */
    public boolean add(T item) throws InterruptedException {
        semaphore.acquire();
        boolean wasAdded = false;
        try {
            wasAdded = items.add(item);
            return wasAdded;
        } finally {
            if (!wasAdded) {
                semaphore.release();
            }
        }
    }

    /**
     * Removes an item from the set.
     *
     * @param item The value to remove from the set.
     * @return true if the item was remove, false otherwise.
     */
    public boolean remove(T item) {
        boolean wasRemoved = items.remove(item);
        if (wasRemoved) {
            semaphore.release();
        }
        return wasRemoved;
    }

    /**
     * Runs this example.
     */
    public static void main(String args[]) {

        final int capacity = 10;
        final int count = 100;
        final BoundedHashSet<Integer> boundedHashSet = new BoundedHashSet<Integer>(capacity);
        final CountDownLatch latch = new CountDownLatch(count);

        Thread adder = new Thread() {
            public void run() {
                try {
                    for (int i=0;i< count;i++) {
                        int current = i % capacity;
                        boundedHashSet.add(new Integer(current));
                        System.out.println("Added "+current+".");
                    }
                } catch (InterruptedException e) {
                    //empty
                }
            }
        };
        adder.start();

        Thread remover = new Thread() {
            public void run() {
                int i = 0;
                while(!Thread.currentThread().isInterrupted()) {
                    int current = i++ % capacity;
                    boolean removed = boundedHashSet.remove(new Integer(current));
                    if (removed) {
                        System.out.println("Removed "+current+".");
                        latch.countDown();
                    }
                }
            }
        };

        remover.start();

        try {
            latch.await();
        } catch (InterruptedException e) {
            //empty
        }
        remover.interrupt();


    }
}